/* 
 * File:   Conexion.cpp
 * Author: max
 * 
 * Created on October 18, 2012, 7:00 PM
 */

#include <iostream>
#include "errno.h"
#include "server_Conexion.h"

using namespace std;
using namespace TP::Comunicacion;

#define PERDIDA_CONEXION 0
#define ERROR_CONEXION -1

Conexion::Conexion(const string& pPuerto, IManejadorConexionEvento* pManejador){
    
    this->puerto = pPuerto;
    this->manejadorConexionEvento = pManejador;
    this->sockfd = -1;
            
}

Conexion::~Conexion() {}

void Conexion::Iniciar() throw (Excepcion&){
    
    try
    {
        if (NULL == this->manejadorConexionEvento)
            throw Excepcion ("No hay definido un manejador de evento de nueva sesion.");
        
        // Configuro la conexion.
        if (-1 == ((this->sockfd = socket(AF_INET, SOCK_STREAM, 0))))
        {
            string msg = "No se puede iniciar el socket. ";
            msg += strerror (errno);
            
            throw Excepcion (msg);
        }
        
        bzero((char *) &serv_addr, sizeof(serv_addr));
        this->serv_addr.sin_family = AF_INET;
        this->serv_addr.sin_addr.s_addr = INADDR_ANY;
        this->serv_addr.sin_port = htons(atoi(this->puerto.c_str()));
        
        if (-1 == bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr))) 
        {
            string msg = "No se puede iniciar el puerto. ";
            msg += strerror (errno);
            throw Excepcion (msg);
        }
        
        if (-1 == listen(sockfd,5))
        {
            string msg = "Error en listen. ";
            msg += strerror(errno);
            throw Excepcion (msg);
        }
        
        this->conexionFinalizada = false;
        
        // Inicio la ejecucion.
        Thread::Iniciar();
    }
    catch (Excepcion& pEx)
    {
        string msg = "No se puede inicar la conexion (Puerto: ";
        msg += this->puerto;
        msg += ").";
        
        pEx.expandirMensaje(msg);
        throw;
    }
}

void Conexion::Finalizar() throw (Excepcion&){
    try
    {
        // Finalizo el socket de conexion.
        this->conexionFinalizada = true;
        
        if (-1 == shutdown(this->sockfd, SHUT_RDWR ))
        {
            this->conexionFinalizada = false;
            
            string msg = "Fallo al finalizar la conexion. ";
            msg += strerror (errno);
            throw Excepcion (msg);
        }
        
        Thread::Finalizar();
    }
    catch (Excepcion& pEx)
    {
        string msg = "No se puede finalizar la conexion (Puerto: " + this->puerto + ").";
        
        pEx.expandirMensaje(msg);
        throw;
    }
}

void Conexion::tarea (){
    
    while (true)
    {
        /* Escucha por conexiones entrantes.  Cuando recibe una nueva conexion
         * instancia una sesion y la declara en el manejador de conexiones.
         * 
         * En caso de perdida de conexion finaliza las operaciones de escucha.
         * En caso de error de conexion pasa al siguiente intento.
         */
            
        struct sockaddr_in* cli_addr = (struct sockaddr_in*) new (struct sockaddr_in);
        socklen_t clilen = sizeof(cli_addr);
        int newsockfd = accept(this->sockfd,(struct sockaddr*) cli_addr, &clilen);
        int errnov = errno;

        if (newsockfd <= 0)
        {
            if (NULL != cli_addr) delete cli_addr;
            
            if (! this->conexionFinalizada)
            {
                string msg = "";
                descripcionErrorConexion(msg, errnov);
                this->manejadorConexionEvento->eventoErrorConexionInexistente((IEnviante*) this, msg); 
            }
            
            break;
        }

        // Llego un nuevo cliente. Instancio la sesion y notifico.

        Sesion* sesion = new Sesion (newsockfd, cli_addr);
        this->manejadorConexionEvento->eventoNuevaSesion((IEnviante*) this, sesion);  
    }
}

void Conexion::descripcionErrorConexion (string &pMensaje, int pCodigoError){
 
    pMensaje = "ERROR_CONNECTION.  ";
    
    switch (pCodigoError)
    {
        case EAGAIN:
           pMensaje += "The socket is marked nonblocking and no connections are present to be accepted. POSIX.1-2001 allows either error to be returned for this case, and does not require these constants to have the same value, so a portable application should check for both possibilities.";
           break;

        case EBADF:
           pMensaje += "The descriptor is invalid.";
           break;
           
        case ECONNABORTED:
           pMensaje += "A connection has been aborted."; 

        case EFAULT:
           pMensaje += "The addr argument is not in a writable part of the user address space."; 
           break;    

        case EINTR:
           pMensaje += "The system call was interrupted by a signal that was caught before a valid connection arrived; see signal(7)."; 
           break;
           
        case EINVAL:
           pMensaje += "Socket is not listening for connections, or addrlen is invalid (e.g., is negative)."; 
           pMensaje += "(accept4()) invalid value in flags."; 
           break;
           
        case EMFILE:
           pMensaje += "The per-process limit of open file descriptors has been reached."; 
           pMensaje += "The system limit on the total number of open files has been reached."; 
           break;

        case ENOBUFS:
        case ENOMEM:
           pMensaje += "Not enough free memory. This often means that the memory allocation is limited by the socket buffer limits, not by the system memory."; 
           break;

        case ENOTSOCK:
           pMensaje += "The descriptor references a file, not a socket."; 
           break;

        case EOPNOTSUPP:
            pMensaje += "The referenced socket is not of type SOCK_STREAM."; 
            break;

        case EPROTO:
            pMensaje += "Protocol error.";
            break;

        case EPERM:
            pMensaje += "Firewall rules forbid connection.";
            break;
        default:
            pMensaje += "Desconocido.";
    }
 
}

